package cn.dansj.service.Impl;

import cn.dansj.bean.FileUploadInfo;
import cn.dansj.common.utils.bean.ResponseMessage;
import cn.dansj.common.utils.bean.Return;
import cn.dansj.common.utils.json.DictMap;
import cn.dansj.common.utils.enums.CreateBucket;
import cn.dansj.enums.GlobalEnv;
import cn.dansj.service.MinioUploadService;
import cn.dansj.common.redis.RedisUtils;
import cn.dansj.common.utils.transfer.GetTime;
import cn.dansj.common.utils.transfer.StringUtils;
import cn.dansj.common.utils.transfer.RandomUtils;
import cn.dansj.common.utils.transfer.Md5Utils;
import cn.dansj.utils.project.ProjectUtils;
import com.alibaba.fastjson2.JSONObject;
import io.minio.StatObjectResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.*;

@Service
public class MinioUploadServiceImpl implements MinioUploadService {
    private final Logger log = LoggerFactory.getLogger(MinioUploadServiceImpl.class);

    private final RedisUtils redisUtils;

    public MinioUploadServiceImpl(RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
    }

    /**
     * 通过 md5 获取已上传的数据（断点续传）
     *
     * @param md5 String
     * @return Mono<Map < String, Object>>
     */
    @Override
    public Return<?> checkFileMd5(String md5, String filename, String bucket) {
        StatObjectResponse fileInfo = ProjectUtils.getMinio().fileInfo(ProjectUtils.getSettingByRedis("minio", "defaultBucket", "project"), md5);

        if (fileInfo != null) {
            //存在文件，直接写入记录到redis
            saveUploadFileInfo(new FileUploadInfo().setFileMd5(md5).setFileName(filename).setBucketName(bucket).setFileSize(fileInfo.size()));
            return Return.T(false, "文件已存在").setResponseCode(900);
        } else {
            //正常上传
            return Return.T(true, "无上传文件").setResponseCode(902);
        }
    }

    /**
     * 文件分片上传
     */
    @Override
    public Return<?> initMultiPartUpload(FileUploadInfo fileUploadInfo) {
        log.info("tip message: 通过 <{}> 开始初始化<分片上传>任务", fileUploadInfo);
        // 获取桶
        String bucketName = ProjectUtils.getSettingByRedis("minio", "defaultBucket", "project");//fileUploadInfo.getBucketName();
        // 单文件上传
        if (fileUploadInfo.getChunkNum() == 1) {
            log.info("bucket:{} tip message: 当前分片数量 <{}> 进行单文件上传", bucketName, fileUploadInfo.getChunkNum());
            ResponseMessage getUploadUrl = ProjectUtils.getMinio().getUploadUrl(bucketName, fileUploadInfo.getFileMd5());
            if (getUploadUrl.getResponseCode() == 0) {
                return Return.T(false, "上传地址获取失败：" + getUploadUrl.getMessage());
            }
            Map<String, Object> map = new HashMap<>();
            map.put("urlList", Collections.singletonList(getUploadUrl.getMessage()));
            map.put("uploadId", "SingleFileUpload");
            return Return.T(map);
        } else {
            // 分片上传
            log.info("tip message: 当前分片数量 <{}> 进行分片上传", fileUploadInfo.getChunkNum());
            Return<Map<String, Object>> aReturn = ProjectUtils.getMinio().initMultiPartUpload(fileUploadInfo, fileUploadInfo.getFileMd5(), fileUploadInfo.getChunkNum(), fileUploadInfo.getContentType(), bucketName);
            Map<String, Object> map = aReturn.getData();
            String uploadId = (String) map.get("uploadId");
            fileUploadInfo.setUploadId(uploadId);
            return aReturn;
        }
    }

    /**
     * 文件合并
     */
    @Override
    public String mergeMultipartUpload(FileUploadInfo fileUploadInfo) {
        log.info("tip message: 通过 <{}> 开始合并<分片上传>任务", fileUploadInfo);
        final String defaultBucket = ProjectUtils.getSettingByRedis("minio", "defaultBucket", "project");
        //合并成功
        if (ProjectUtils.getMinio().mergeMultipartUpload(fileUploadInfo.getFileMd5(), fileUploadInfo.getUploadId(), defaultBucket)) {
            //存入数据库
            saveUploadFileInfo(fileUploadInfo);
            return "合并成功";
        }
        return null;
    }

    @Override
    public String getFilePath(String bucketName, String fileName) {
        return ProjectUtils.getMinio().getFilePath(bucketName, fileName);
    }

    @Override
    public String upload(MultipartFile file, String bucket, String filename) throws IOException {
        String md5 = Md5Utils.md5(file.getBytes());

        final String defaultBucket = ProjectUtils.getSettingByRedis("minio", "defaultBucket", "project");
        StatObjectResponse fileInfo = ProjectUtils.getMinio().fileInfo(defaultBucket, md5);

        if (fileInfo != null) {
            //存在文件，直接写入记录到redis
            return saveUploadFileInfo(new FileUploadInfo().setFileMd5(md5).setFileName(filename).setBucketName(bucket).setFileSize(fileInfo.size()));
        } else {
            //正常上传
            final ResponseMessage responseMessage = ProjectUtils.getMinio().upload(defaultBucket, file, md5);
            if (responseMessage.getStatus()) {
                return saveUploadFileInfo(new FileUploadInfo().setFileMd5(md5).setFileName(filename).setBucketName(bucket).setFileSize(file.getSize()));
            }
            return null;
        }
    }

    @Override
    public String saveUploadFileInfo(FileUploadInfo fileUploadInfo) {
        //存入数据库
        final JSONObject minioFile = new JSONObject();
        final String uid = RandomUtils.getUid();
        minioFile.put("id", uid);
        minioFile.put("createTime", GetTime.dbTimestamp());
        minioFile.put("bucketName", fileUploadInfo.getBucketName());
        minioFile.put("fileSize", fileUploadInfo.getFileSize());
        minioFile.put("fileMd5", fileUploadInfo.getFileMd5());
        minioFile.put("fileName", fileUploadInfo.getFileName());
        final JSONObject obj = DictMap.from(redisUtils.hgetall(ProjectUtils.getBucketName(fileUploadInfo.getBucketName())));
        final JSONObject files = obj.getJSONObject("files");
        files.put(uid, minioFile);
        obj.put("files", files);
        redisUtils.hmset(ProjectUtils.getBucketName(fileUploadInfo.getBucketName()), obj);
        return uid;
    }

    @Override
    public List<Map<String, Object>> getTagInfo() {
        final List<String> keys = redisUtils.keys(GlobalEnv.MinioKeyPrefix + "*");
        List<Map<String, Object>> tagInfo = new ArrayList<>();
        for (String key : keys) {
            Map<String, Object> tag = new HashMap<>();

            final JSONObject fileMap = DictMap.from(redisUtils.hgetall(key));
            final JSONObject files = fileMap.getJSONObject("files");
            tag.put("files", files.size());
            tag.put("size", files.values().stream().mapToLong(e -> JSONObject.parseObject(e.toString()).getIntValue("fileSize")).sum());
            tag.put("creationDate", fileMap.get("creationDate"));
            tag.put("name", StringUtils.replace(key, GlobalEnv.MinioKeyPrefix, "", 1));
            tagInfo.add(tag);
        }
        return tagInfo;
    }

    @Override
    public CreateBucket createBucket(String bucket) {
        try {
            final boolean hasKey = redisUtils.hasKey(ProjectUtils.getBucketName(bucket));
            if (hasKey) return CreateBucket.Exist;
            Map<String, Object> map = new HashMap<>();
            map.put("creationDate", System.currentTimeMillis());
            map.put("files", "{}");
            redisUtils.hmset(ProjectUtils.getBucketName(bucket), map);
            return CreateBucket.Success;
        } catch (Exception exception) {
            return CreateBucket.Error;
        }
    }

    @Override
    public boolean deleteBucket(String bucket) {
        JSONObject objectMap = redisUtils.hgetallJSON(ProjectUtils.getBucketName(bucket));
        final JSONObject files = objectMap.getJSONObject("files");
        if (files.size() > 0) {
            return false;
        } else {
            return redisUtils.delete(ProjectUtils.getBucketName(bucket));
        }
    }

    @Override
    public List<Map<String, Object>> getFiles(String bucket) {
        final JSONObject obj = DictMap.from(redisUtils.hgetall(ProjectUtils.getBucketName(bucket)));

        List<Map<String, Object>> files = new ArrayList<>();

        final JSONObject fileObject = obj.getJSONObject("files");

        for (String fileId : fileObject.keySet()) {
            final JSONObject fileMap = fileObject.getJSONObject(fileId);
            Map<String, Object> output = new HashMap<>();
            output.put("size", fileMap.get("fileSize"));
            output.put("filename", fileMap.get("fileName"));
            output.put("uploadTime", fileMap.get("createTime"));
            output.put("bucket", fileMap.get("bucketName"));
            output.put("id", fileMap.get("id"));
            files.add(output);
        }

        return files;
    }
}